﻿using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace Black.Minecraft.Editor.Behaviors
{
    /// <summary>
    /// Behavior permettant de binder une commande sur un événement
    /// </summary>
    public class EventToCommandBehavior : Behavior<UIElement>
    {
        #region Propriétés de dépendances

        public static readonly DependencyProperty AutoHandleProperty = DependencyProperty.Register("AutoHandle", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));
        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventToCommandBehavior), new PropertyMetadata(null));
        public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null));
        public static readonly DependencyProperty ThrowEventParamProperty = DependencyProperty.Register("ThrowEventParam", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(true));

        #endregion

        #region Propriétés

        /// <summary>
        /// Obtient ou définit si on capture systématiquement l'événément
        /// </summary>
        public bool AutoHandle
        {
            get
            {
                return ((bool)this.GetValue(EventToCommandBehavior.AutoHandleProperty));
            }
            set
            {
                this.SetValue(EventToCommandBehavior.AutoHandleProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit la commande a éxécuter sur l'événement
        /// </summary>
        public ICommand Command
        {
            get
            {
                return ((ICommand)this.GetValue(EventToCommandBehavior.CommandProperty));
            }
            set
            {
                this.SetValue(EventToCommandBehavior.CommandProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le paramètre de la commande a éxécuter sur l'événement
        /// </summary>
        public object CommandParameter
        {
            get
            {
                return ((object)this.GetValue(EventToCommandBehavior.CommandParameterProperty));
            }
            set
            {
                this.SetValue(EventToCommandBehavior.CommandParameterProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le nom de l'événement à traiter
        /// </summary>
        public string Event
        {
            get
            {
                return ((string)this.GetValue(EventToCommandBehavior.EventProperty));
            }
            set
            {
                this.SetValue(EventToCommandBehavior.EventProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit si le paramètre de l'événement est envoyé si le
        /// paramètre de la commande est NULL
        /// </summary>
        public bool ThrowEventParam
        {
            get
            {
                return ((bool)this.GetValue(EventToCommandBehavior.ThrowEventParamProperty));
            }
            set
            {
                this.SetValue(EventToCommandBehavior.ThrowEventParamProperty, value);
            }
        }

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public EventToCommandBehavior()
            : base()
        {
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Se produit sur l'attachement entre l'instance et le behavior
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

            // On s'attache

            if (this.AssociatedObject != null && !string.IsNullOrEmpty(this.Event))
                this.BindToEvent();
        }

        /// <summary>
        /// Se produit sur le détachement entre l'instance et le behavior
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            // On se détache

            if (this.AssociatedObject != null && !string.IsNullOrEmpty(this.Event))
                this.UnbindToEvent();
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Attache la référence à l'événement
        /// </summary>
        private void BindToEvent()
        {
            // Récupération du type

            Type origin = this.AssociatedObject.GetType();

            // Récupération de l'événement

            EventInfo info = origin.GetEvent(this.Event);

            if (info == null)
                return;

            // Récupération de la signature du callback générique

            MethodInfo i = this.GetType().GetMethod("EventCallback", BindingFlags.NonPublic | BindingFlags.Instance);

            // Création du délégué orienter le callback vers la bonne fonction

            Delegate d = Delegate.CreateDelegate(info.EventHandlerType, this, i);

            // Ajout du handler sur l'objet

            info.AddEventHandler(this.AssociatedObject, d);
        }

        /// <summary>
        /// Récupère le callback de l'événement
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void EventCallback(object sender, RoutedEventArgs e)
        {
            // Auto capture

            if (this.AutoHandle)
                e.Handled = true;

            // Exécution de la commande

            if (this.Command != null && this.Command.CanExecute(this.CommandParameter))
            {
                if (this.CommandParameter == null && this.ThrowEventParam)
                    this.Command.Execute(e);
                else
                    this.Command.Execute(this.CommandParameter);
            }
        }

        /// <summary>
        /// Déttache la référence à l'événement
        /// </summary>
        private void UnbindToEvent()
        {
            // Récupération du type

            Type origin = this.AssociatedObject.GetType();

            // Récupération de l'événement

            EventInfo info = origin.GetEvent(this.Event);

            if (info == null)
                return;

            // Récupération de la signature du callback générique

            MethodInfo i = this.GetType().GetMethod("EventCallback", BindingFlags.NonPublic | BindingFlags.Instance);

            // Création du délégué orienter le callback vers la bonne fonction

            Delegate d = Delegate.CreateDelegate(info.EventHandlerType, this, i);

            // Ajout du handler sur l'objet

            info.RemoveEventHandler(this.AssociatedObject, d);
        }

        #endregion
    }
}
